/*!	 mptr_ffmpeg.cpp
**	 ppm Target Module
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007 Chris Moore
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
** === N O T E S ===========================================================
**
** ========================================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <ETL/stringf>
#include "mptr_ffmpeg.h"
#include <cstdio>
#include <sys/types.h>
#include <synfig/general.h>
#include <synfig/localization.h>
#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#if HAVE_IO_H
#include <io.h>
#endif
#if HAVE_PROCESS_H
#include <process.h>
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <unistd.h>
#include <iostream>
#include <algorithm>
#include <functional>
#include <ETL/stringf>
#endif

using namespace synfig;
using namespace std;
using namespace etl;

#if defined(HAVE_FORK) && defined(HAVE_PIPE) && defined(HAVE_WAITPID)
#define UNIX_PIPE_TO_PROCESSES
#else
#define WIN32_PIPE_TO_PROCESSES
#endif

SYNFIG_IMPORTER_INIT(ffmpeg_mptr);
SYNFIG_IMPORTER_SET_NAME(ffmpeg_mptr, "ffmpeg");
SYNFIG_IMPORTER_SET_EXT(ffmpeg_mptr, "avi");
SYNFIG_IMPORTER_SET_VERSION(ffmpeg_mptr, "0.1");
SYNFIG_IMPORTER_SET_CVS_ID(ffmpeg_mptr, "$Id$");
SYNFIG_IMPORTER_SET_SUPPORTS_FILE_SYSTEM_WRAPPER(ffmpeg_mptr, false);

bool ffmpeg_mptr::is_animated()
{
    return true;
}

bool
ffmpeg_mptr::seek_to(int frame)
{
    if (frame < cur_frame || !file) {
        if (file) {
#if defined(WIN32_PIPE_TO_PROCESSES)
            pclose(file);
#elif defined(UNIX_PIPE_TO_PROCESSES)
            fclose(file);
            int status;
            waitpid(pid, &status, 0);
#endif
        }

#if defined(WIN32_PIPE_TO_PROCESSES)

        string command;

        String binary_path = synfig::get_binary_path("");

        if (binary_path != "") {
            binary_path = etl::dirname(binary_path) + ETL_DIRECTORY_SEPARATOR;
        }

        binary_path += "ffmpeg.exe";

        command = strprintf("\"%s\" -ss 00:00:00.%d -i \"%s\" -an -f image2pipe -vcodec ppm -\n", binary_path.c_str(), frame, identifier.filename.c_str());

        // This covers the dumb cmd.exe behavior.
        command = "\"" + command + "\"";

        file = popen(command.c_str(), POPEN_BINARY_READ_TYPE);

#elif defined(UNIX_PIPE_TO_PROCESSES)

        int p[2];

        if (pipe(p)) {
            cerr << "Unable to open pipe to ffmpeg (no pipe)" << endl;
            return false;
        };

        pid = fork();

        if (pid == -1) {
            cerr << "Unable to open pipe to ffmpeg (pid == -1)" << endl;
            return false;
        }

        if (pid == 0) {
            // Child process
            // Close pipein, not needed
            close(p[0]);

            // Dup pipein to stdout
            if (dup2(p[1], STDOUT_FILENO) == -1) {
                cerr << "Unable to open pipe to ffmpeg (dup2( p[1], STDOUT_FILENO ) == -1)" << endl;
                return false;
            }

            // Close the unneeded pipein
            close(p[1]);
            string time = strprintf("00:00:00.%d", frame);
            execlp("ffmpeg", "ffmpeg", "-ss", time.c_str(), "-i", identifier.filename.c_str(), "-an", "-f", "image2pipe", "-vcodec", "ppm", "-", (const char *)NULL);
            // We should never reach here unless the exec failed
            cerr << "Unable to open pipe to ffmpeg (exec failed)" << endl;
            _exit(1);
        } else {
            // Parent process
            // Close pipeout, not needed
            close(p[1]);
            // Save pipein to file handle, will read from it later
            file = fdopen(p[0], "rb");
        }

#else
#error There are no known APIs for creating child processes
#endif

        if (!file) {
            cerr << "Unable to open pipe to ffmpeg" << endl;
            return false;
        }

        cur_frame = -1;
    }

    while (cur_frame < frame - 1) {
        cerr << "Seeking to..." << frame << '(' << cur_frame << ')' << endl;

        if (!grab_frame()) {
            return false;
        }
    }

    return true;
}

bool
ffmpeg_mptr::grab_frame(void)
{
    if (!file) {
        cerr << "unable to open " << identifier.filename.c_str() << endl;
        return false;
    }

    int w, h;
    float divisor;
    char cookie[2];
    cookie[0] = fgetc(file);

    if (feof(file)) {
        return false;
    }

    cookie[1] = fgetc(file);

    if (cookie[0] != 'P' || cookie[1] != '6') {
        cerr << "stream not in PPM format \"" << cookie[0] << cookie[1] << '"' << endl;
        return false;
    }

    fgetc(file);
    fscanf(file, "%d %d\n", &w, &h);
    fscanf(file, "%f", &divisor);
    fgetc(file);

    if (feof(file)) {
        return false;
    }

    int x;
    int y;
    frame.set_wh(w, h);

    for (y = 0; y < frame.get_h(); y++)
        for (x = 0; x < frame.get_w(); x++) {
            if (feof(file)) {
                return false;
            }

            float r = gamma().r_U8_to_F32((unsigned char)fgetc(file));
            float g = gamma().g_U8_to_F32((unsigned char)fgetc(file));
            float b = gamma().b_U8_to_F32((unsigned char)fgetc(file));
            frame[y][x] = Color(
                              r,
                              g,
                              b,
                              1.0
                          );
        }

    cur_frame++;
    return true;
}

ffmpeg_mptr::ffmpeg_mptr(const synfig::FileSystem::Identifier &identifier):
    synfig::Importer(identifier)
{
    pid = -1;
#ifdef HAVE_TERMIOS_H
    tcgetattr(0, &oldtty);
#endif
    file = NULL;
    fps = 23.98;
    cur_frame = -1;
}

ffmpeg_mptr::~ffmpeg_mptr()
{
    if (file) {
#if defined(WIN32_PIPE_TO_PROCESSES)
        pclose(file);
#elif defined(UNIX_PIPE_TO_PROCESSES)
        fclose(file);
        int status;
        waitpid(pid, &status, 0);
#endif
    }

#ifdef HAVE_TERMIOS_H
    tcsetattr(0, TCSANOW, &oldtty);
#endif
}

bool
ffmpeg_mptr::get_frame(synfig::Surface &surface, const synfig::RendDesc &/*renddesc*/, Time time, synfig::ProgressCallback *)
{
    int i = (int)(time * fps);

    if (i != cur_frame) {
        if (!seek_to(i)) {
            return false;
        }

        if (!grab_frame()) {
            return false;
        }
    }

    surface = frame;
    return true;
}